Index: usr.sbin/freebsd-update/freebsd-update.8
===================================================================
--- usr.sbin/freebsd-update/freebsd-update.8	(revision 282245)
+++ usr.sbin/freebsd-update/freebsd-update.8	(working copy)
@@ -25,7 +25,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd July 14, 2010
+.Dd March 2, 2015
 .Dt FREEBSD-UPDATE 8
 .Os FreeBSD
 .Sh NAME
@@ -36,10 +36,12 @@
 .Op Fl b Ar basedir
 .Op Fl d Ar workdir
 .Op Fl f Ar conffile
+.Op Fl F
 .Op Fl k Ar KEY
 .Op Fl r Ar newrelease
 .Op Fl s Ar server
 .Op Fl t Ar address
+.Op Fl -not-running-from-cron
 .Cm command ...
 .Sh DESCRIPTION
 The
@@ -49,21 +51,21 @@ updates to the FreeBSD base system.
 Note that updates are only available if they are being built for the
 FreeBSD release and architecture being used; in particular, the
 .Fx
-Security Team only builds updates for releases shipped in binary form 
+Security Team only builds updates for releases shipped in binary form
 by the
 .Fx
 Release Engineering Team, e.g.,
 .Fx
-7.3-RELEASE and
+9.3-RELEASE and
 .Fx
-8.0, but not
+10.1-RELEASE, but not
 .Fx
-6.3-STABLE or
+9.3-STABLE or
 .Fx
-9.0-CURRENT.
+11-CURRENT.
 .Sh OPTIONS
 The following options are supported:
-.Bl -tag -width "-f conffile"
+.Bl -tag -width "-r newrelease"
 .It Fl b Ar basedir
 Operate on a system mounted at
 .Ar basedir .
@@ -81,6 +83,10 @@ Read configuration options from
 .Ar conffile .
 (default:
 .Pa /etc/freebsd-update.conf )
+.It Fl F
+Force
+.Nm Cm fetch
+to proceed where it normally would not, such as an unfinished upgrade
 .It Fl k Ar KEY
 Trust an RSA key with SHA256 of
 .Ar KEY .
@@ -98,13 +104,21 @@ Mail output of
 command, if any, to
 .Ar address .
 (default: root, or as given in the configuration file.)
+.It Fl -not-running-from-cron
+Force
+.Nm Cm fetch
+to proceed when there is no controlling tty.
+This is for use by automated scripts and orchestration tools.
+Please do not run
+.Nm Cm fetch
+from crontab or similar using this flag, see:
+.Nm Cm cron
 .El
 .Sh COMMANDS
 The
 .Cm command
 can be any one of the following:
-.Pp
-.Bl -tag -width "-f conffile"
+.Bl -tag -width "rollback"
 .It Cm fetch
 Based on the currently installed world and the configuration
 options set, fetch all available binary updates.
@@ -128,6 +142,11 @@ Fetch files necessary for upgrading to a
 Before using this command, make sure that you read the
 announcement and release notes for the new release in
 case there are any special steps needed for upgrading.
+Note that this command may require up to 500 MB of space in
+.Ar workdir
+depending on which components of the
+.Fx
+base system are installed.
 .It Cm install
 Install the most recently fetched updates or upgrade.
 .It Cm rollback
@@ -149,7 +168,7 @@ other than 3AM, to avoid overly imposing
 on the server(s) hosting the updates.
 .It
 In spite of its name,
-.Cm
+.Nm
 IDS should not be relied upon as an "Intrusion Detection
 System", since if the system has been tampered with
 it cannot be trusted to operate correctly.
@@ -158,11 +177,11 @@ purposes, make sure you boot from a secu
 .El
 .Sh FILES
 .Bl -tag -width "/etc/freebsd-update.conf"
-.It /etc/freebsd-update.conf
+.It Pa /etc/freebsd-update.conf
 Default location of the
 .Nm
 configuration file.
-.It /var/db/freebsd-update/
+.It Pa /var/db/freebsd-update/
 Default location where
 .Nm
 stores temporary files and downloaded updates.
@@ -170,4 +189,4 @@ stores temporary files and downloaded up
 .Sh SEE ALSO
 .Xr freebsd-update.conf 5
 .Sh AUTHORS
-.An Colin Percival Aq cperciva@FreeBSD.org
+.An Colin Percival Aq Mt cperciva@FreeBSD.org
Index: usr.sbin/freebsd-update/freebsd-update.sh
===================================================================
--- usr.sbin/freebsd-update/freebsd-update.sh	(revision 282245)
+++ usr.sbin/freebsd-update/freebsd-update.sh	(working copy)
@@ -43,12 +43,15 @@ Options:
                   (default: /var/db/freebsd-update/)
   -f conffile  -- Read configuration options from conffile
                   (default: /etc/freebsd-update.conf)
+  -F           -- Force a fetch operation to proceed
   -k KEY       -- Trust an RSA key with SHA256 hash of KEY
   -r release   -- Target for upgrade (e.g., 6.2-RELEASE)
   -s server    -- Server from which to fetch updates
                   (default: update.FreeBSD.org)
   -t address   -- Mail output of cron command, if any, to address
                   (default: root)
+  --not-running-from-cron
+               -- Run without a tty, for use by automated tools
 Commands:
   fetch        -- Fetch updates from server
   cron         -- Sleep rand(3600) seconds, fetch updates, and send an
@@ -284,6 +287,9 @@ config_TargetRelease () {
 	else
 		return 1
 	fi
+	if echo ${TARGETRELEASE} | grep -qE '^[0-9.]+$'; then
+		TARGETRELEASE="${TARGETRELEASE}-RELEASE"
+	fi
 }
 
 # Define what happens to output of utilities
@@ -396,6 +402,12 @@ init_params () {
 
 	# No commands specified yet
 	COMMANDS=""
+
+	# Force fetch to proceed
+	FORCEFETCH=0
+
+	# Run without a TTY
+	NOTTYOK=0
 }
 
 # Parse the command line
@@ -408,6 +420,12 @@ parse_cmdline () {
 			if [ ! -z "${CONFFILE}" ]; then usage; fi
 			shift; CONFFILE="$1"
 			;;
+		-F)
+			FORCEFETCH=1
+			;;
+		--not-running-from-cron)
+			NOTTYOK=1
+			;;
 
 		# Configuration file equivalents
 		-b)
@@ -569,7 +587,7 @@ fetch_setup_verboselevel () {
 # running *-p[0-9]+, strip off the last part; if the
 # user is running -SECURITY, call it -RELEASE.  Chdir
 # into the working directory.
-fetch_check_params () {
+fetchupgrade_check_params () {
 	export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
 
 	_SERVERNAME_z=\
@@ -577,6 +595,7 @@ fetch_check_params () {
 	_KEYPRINT_z="Key must be given via -k option or configuration file."
 	_KEYPRINT_bad="Invalid key fingerprint: "
 	_WORKDIR_bad="Directory does not exist or is not writable: "
+	_WORKDIR_bad2="Directory is not on a persistent filesystem: "
 
 	if [ -z "${SERVERNAME}" ]; then
 		echo -n "`basename $0`: "
@@ -600,6 +619,13 @@ fetch_check_params () {
 		echo ${WORKDIR}
 		exit 1
 	fi
+	case `df -T ${WORKDIR}` in */dev/md[0-9]* | *tmpfs*)
+		echo -n "`basename $0`: "
+		echo -n "${_WORKDIR_bad2}"
+		echo ${WORKDIR}
+		exit 1
+		;;
+	esac
 	chmod 700 ${WORKDIR}
 	cd ${WORKDIR} || exit 1
 
@@ -652,9 +678,29 @@ fetch_check_params () {
 	BDHASH=`echo ${BASEDIR} | sha256 -q`
 }
 
+# Perform sanity checks etc. before fetching updates.
+fetch_check_params () {
+	fetchupgrade_check_params
+
+	if ! [ -z "${TARGETRELEASE}" ]; then
+		echo -n "`basename $0`: "
+		echo -n "-r option is meaningless with 'fetch' command.  "
+		echo "(Did you mean 'upgrade' instead?)"
+		exit 1
+	fi
+
+	# Check that we have updates ready to install
+	if [ -f ${BDHASH}-install/kerneldone -a $FORCEFETCH -eq 0 ]; then
+		echo "You have a partially completed upgrade pending"
+		echo "Run '$0 install' first."
+		echo "Run '$0 fetch -F' to proceed anyway."
+		exit 1
+	fi
+}
+
 # Perform sanity checks etc. before fetching upgrades.
 upgrade_check_params () {
-	fetch_check_params
+	fetchupgrade_check_params
 
 	# Unless set otherwise, we're upgrading to the same kernel config.
 	NKERNCONF=${KERNCONF}
@@ -1185,7 +1231,7 @@ fetch_metadata_sanity () {
 	# Some aliases to save space later: ${P} is a character which can
 	# appear in a path; ${M} is the four numeric metadata fields; and
 	# ${H} is a sha256 hash.
-	P="[-+./:=%@_[~[:alnum:]]"
+	P="[-+./:=,%@_[~[:alnum:]]"
 	M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
 	H="[0-9a-f]{64}"
 
@@ -1456,7 +1502,7 @@ fetch_inspect_system () {
 	    sort -k 3,3 -t '|' > $2.tmp
 	rm filelist
 
-	# Check if an error occured during system inspection
+	# Check if an error occurred during system inspection
 	if [ -f .err ]; then
 		return 1
 	fi
@@ -2240,6 +2286,19 @@ upgrade_oldall_to_oldnew () {
 	mv $2 $3
 }
 
+# Helper for upgrade_merge: Return zero true iff the two files differ only
+# in the contents of their RCS tags.
+samef () {
+	X=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $1 | ${SHA256}`
+	Y=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $2 | ${SHA256}`
+
+	if [ $X = $Y ]; then
+		return 0;
+	else
+		return 1;
+	fi
+}
+
 # From the list of "old" files in $1, merge changes in $2 with those in $3,
 # and update $3 to reflect the hashes of merged files.
 upgrade_merge () {
@@ -2323,6 +2382,14 @@ upgrade_merge () {
 
 		# Ask the user to handle any files which didn't merge.
 		while read F; do
+			# If the installed file differs from the version in
+			# the old release only due to RCS tag expansion
+			# then just use the version in the new release.
+			if samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
+				cp merge/${RELNUM}/${F} merge/new/${F}
+				continue
+			fi
+
 			cat <<-EOF
 
 The following file could not be merged automatically: ${F}
@@ -2337,9 +2404,18 @@ manually...
 		# Ask the user to confirm that he likes how the result
 		# of merging files.
 		while read F; do
-			# Skip files which haven't changed.
-			if [ -f merge/new/${F} ] &&
-			    cmp -s merge/old/${F} merge/new/${F}; then
+			# Skip files which haven't changed except possibly
+			# in their RCS tags.
+			if [ -f merge/old/${F} ] && [ -f merge/new/${F} ] &&
+			    samef merge/old/${F} merge/new/${F}; then
+				continue
+			fi
+
+			# Skip files where the installed file differs from
+			# the old file only due to RCS tags.
+			if [ -f merge/old/${F} ] &&
+			    [ -f merge/${OLDRELNUM}/${F} ] &&
+			    samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
 				continue
 			fi
 
@@ -2526,6 +2602,10 @@ upgrade_run () {
 	# Leave a note behind to tell the "install" command that the kernel
 	# needs to be installed before the world.
 	touch ${BDHASH}-install/kernelfirst
+
+	# Remind the user that they need to run "freebsd-update install"
+	# to install the downloaded bits, in case they didn't RTFM.
+	echo "To install the downloaded upgrades, run \"$0 install\"."
 }
 
 # Make sure that all the file hashes mentioned in $@ have corresponding
@@ -2577,14 +2657,14 @@ backup_kernel_finddir () {
 	while true ; do
 		# Pathname does not exist, so it is OK use that name
 		# for backup directory.
-		if [ ! -e $BACKUPKERNELDIR ]; then
+		if [ ! -e $BASEDIR/$BACKUPKERNELDIR ]; then
 			return 0
 		fi
 
 		# If directory do exist, we only use if it has our
 		# marker file.
-		if [ -d $BACKUPKERNELDIR -a \
-			-e $BACKUPKERNELDIR/.freebsd-update ]; then
+		if [ -d $BASEDIR/$BACKUPKERNELDIR -a \
+			-e $BASEDIR/$BACKUPKERNELDIR/.freebsd-update ]; then
 			return 0
 		fi
 
@@ -2592,7 +2672,7 @@ backup_kernel_finddir () {
 		# the end and try again.
 		CNT=$((CNT + 1))
 		if [ $CNT -gt 9 ]; then
-			echo "Could not find valid backup dir ($BACKUPKERNELDIR)"
+			echo "Could not find valid backup dir ($BASEDIR/$BACKUPKERNELDIR)"
 			exit 1
 		fi
 		BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`"
@@ -2619,17 +2699,17 @@ backup_kernel () {
 	# Remove old kernel backup files.  If $BACKUPKERNELDIR was
 	# "not ours", backup_kernel_finddir would have exited, so
 	# deleting the directory content is as safe as we can make it.
-	if [ -d $BACKUPKERNELDIR ]; then
-		rm -fr $BACKUPKERNELDIR
+	if [ -d $BASEDIR/$BACKUPKERNELDIR ]; then
+		rm -fr $BASEDIR/$BACKUPKERNELDIR
 	fi
 
 	# Create directories for backup.
-	mkdir -p $BACKUPKERNELDIR
-	mtree -cdn -p "${KERNELDIR}" | \
-	    mtree -Ue -p "${BACKUPKERNELDIR}" > /dev/null
+	mkdir -p $BASEDIR/$BACKUPKERNELDIR
+	mtree -cdn -p "${BASEDIR}/${KERNELDIR}" | \
+	    mtree -Ue -p "${BASEDIR}/${BACKUPKERNELDIR}" > /dev/null
 
 	# Mark the directory as having been created by freebsd-update.
-	touch $BACKUPKERNELDIR/.freebsd-update
+	touch $BASEDIR/$BACKUPKERNELDIR/.freebsd-update
 	if [ $? -ne 0 ]; then
 		echo "Could not create kernel backup directory"
 		exit 1
@@ -2647,8 +2727,8 @@ backup_kernel () {
 	fi
 
 	# Backup all the kernel files using hardlinks.
-	(cd $KERNELDIR && find . -type f $FINDFILTER -exec \
-	    cp -pl '{}' ${BACKUPKERNELDIR}/'{}' \;)
+	(cd ${BASEDIR}/${KERNELDIR} && find . -type f $FINDFILTER -exec \
+	    cp -pl '{}' ${BASEDIR}/${BACKUPKERNELDIR}/'{}' \;)
 
 	# Re-enable patchname expansion.
 	set +f
@@ -2746,7 +2826,7 @@ install_files () {
 
 		# Update linker.hints if necessary
 		if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
-			kldxref -R /boot/ 2>/dev/null
+			kldxref -R ${BASEDIR}/boot/ 2>/dev/null
 		fi
 
 		# We've finished updating the kernel.
@@ -2797,14 +2877,14 @@ Kernel updates have been installed.  Ple
 		install_delete INDEX-OLD INDEX-NEW || return 1
 
 		# Rebuild /etc/spwd.db and /etc/pwd.db if necessary.
-		if [ /etc/master.passwd -nt /etc/spwd.db ] ||
-		    [ /etc/master.passwd -nt /etc/pwd.db ]; then
-			pwd_mkdb /etc/master.passwd
+		if [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/spwd.db ] ||
+		    [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/pwd.db ]; then
+			pwd_mkdb -d ${BASEDIR}/etc ${BASEDIR}/etc/master.passwd
 		fi
 
 		# Rebuild /etc/login.conf.db if necessary.
-		if [ /etc/login.conf -nt /etc/login.conf.db ]; then
-			cap_mkdb /etc/login.conf
+		if [ ${BASEDIR}/etc/login.conf -nt ${BASEDIR}/etc/login.conf.db ]; then
+			cap_mkdb ${BASEDIR}/etc/login.conf
 		fi
 
 		# We've finished installing the world and deleting old files
@@ -3011,21 +3091,8 @@ IDS_compare () {
 	mv INDEX-NOTMATCHING.tmp INDEX-NOTMATCHING
 
 	# Go through the lines and print warnings.
-	while read LINE; do
-		FPATH=`echo "${LINE}" | cut -f 1 -d '|'`
-		TYPE=`echo "${LINE}" | cut -f 2 -d '|'`
-		OWNER=`echo "${LINE}" | cut -f 3 -d '|'`
-		GROUP=`echo "${LINE}" | cut -f 4 -d '|'`
-		PERM=`echo "${LINE}" | cut -f 5 -d '|'`
-		HASH=`echo "${LINE}" | cut -f 6 -d '|'`
-		LINK=`echo "${LINE}" | cut -f 7 -d '|'`
-		P_TYPE=`echo "${LINE}" | cut -f 8 -d '|'`
-		P_OWNER=`echo "${LINE}" | cut -f 9 -d '|'`
-		P_GROUP=`echo "${LINE}" | cut -f 10 -d '|'`
-		P_PERM=`echo "${LINE}" | cut -f 11 -d '|'`
-		P_HASH=`echo "${LINE}" | cut -f 12 -d '|'`
-		P_LINK=`echo "${LINE}" | cut -f 13 -d '|'`
-
+	local IFS='|'
+	while read FPATH TYPE OWNER GROUP PERM HASH LINK P_TYPE P_OWNER P_GROUP P_PERM P_HASH P_LINK; do
 		# Warn about different object types.
 		if ! [ "${TYPE}" = "${P_TYPE}" ]; then
 			echo -n "${FPATH} is a "
@@ -3153,7 +3220,7 @@ get_params () {
 # Fetch command.  Make sure that we're being called
 # interactively, then run fetch_check_params and fetch_run
 cmd_fetch () {
-	if [ ! -t 0 ]; then
+	if [ ! -t 0 -a $NOTTYOK -eq 0 ]; then
 		echo -n "`basename $0` fetch should not "
 		echo "be run non-interactively."
 		echo "Run `basename $0` cron instead."
